use ext::no_std::pipelines::{pipe::Pipe, tap::Tap};
use std::fs;
use tar::{Archive, Builder};

fn mark_file_or_dir(r#in: &str, vec: &mut Vec<u8>) {
    let meta = fs::metadata(r#in).unwrap();
    if meta.is_dir() {
        vec.push(0)
    } else if meta.is_file() {
        vec.push(1)
    } else {
        panic!("unsupported file type")
    }
}

fn judge_file_or_dir(
    last: u8,
    byt: Vec<u8>,
    f1: impl FnOnce(&mut Archive<&[u8]>),
    f2: impl FnOnce(&mut Archive<&[u8]>),
) {
    let tar = &mut Archive::new(byt.as_slice());
    match last {
        0 => f1(tar),
        1 => f2(tar),
        _ => panic!("file type error"),
    }
}

fn archive(r#in: &str, file_ext_name: &str, f: impl FnOnce(Vec<u8>) -> Vec<u8>) {
    Builder::new(vec![])
        .tap_mut(|b| {
            b.append_dir_all("", r#in)
                .unwrap_or_else(|_| b.append_path(r#in).unwrap())
        }).into_inner().unwrap()
        .pipe(f)
        .tap_mut(|vec| mark_file_or_dir(r#in, vec)) // 0: dir, 1: file
        .pipe(|byt| fs::write(format!("{in}{file_ext_name}"), byt).unwrap())
}

fn restore(r#in: &str, file_ext_name: &str, f: impl FnOnce(Vec<u8>) -> Vec<u8>) {
    fs::read(r#in)
        .unwrap()
        .pipe(|mut v| (v.pop().unwrap(), v))
        .pipe(|(last, data)| (f(data), last))
        .pipe(|(byt, last)| {
            r#in.trim_end_matches(file_ext_name).pipe(|name| {
                judge_file_or_dir(
                    last,
                    byt,
                    |tar| tar.unpack(name).unwrap(),
                    |tar| {
                        tar.entries().unwrap().for_each(|file| {
                            file.unwrap().unpack(name).unwrap();
                        })
                    },
                )
            })
        })
}

pub fn pack<'a, F1, F2>(r#in: &'a str) -> impl FnOnce(&'a str, F1, F2) where F1: FnOnce(Vec<u8>) -> Vec<u8>, F2: FnOnce(Vec<u8>) -> Vec<u8> {
    |file_ext_name, compress, encrypt|
        archive(r#in, file_ext_name, |data| data.pipe(compress).pipe(encrypt))
}

pub fn unpack<'a, F1, F2>(r#in: &'a str) -> impl FnOnce(&'a str, F1, F2) where F1: FnOnce(Vec<u8>) -> Vec<u8>, F2: FnOnce(Vec<u8>) -> Vec<u8> {
    |file_ext_name, decompress, decrypt|
        restore(r#in, file_ext_name, |data| data.pipe(decrypt).pipe(decompress))
}